package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: 李旭
 * @create: 2021-09-03 09:04
 **/
@Service
public class ListServiceImpl implements ListService {

    //  ElasticsearchRestTemplate ： 在查询方面速度快  好用
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private GoodsDao goodsDao;//ES
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;


    //上架
    @Override
    public void onSale(Long skuId) {
        Goods goods = new Goods();
        //将上面对象中所需要的数据全都查询出来并设置到Goods中
        //1: 库存表  sku_info
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setPrice(skuInfo.getPrice().doubleValue());//BigDecimal : 关于钱方面的类型  精确度  双精度
        //2:添加时间
        //当前时间
        Calendar calendar = Calendar.getInstance();
        //System.out.println("当前时间：" + calendar.getTime());
        //calendar.add(Calendar.HOUR,4);//在当前时间基础上 添加4小时  未来4小时的时间
        //System.out.println("未来4个小时之后的时间：" + calendar.getTime());
        goods.setCreateTime(calendar.getTime());// new Date()
        //3:品牌表 base_trademark
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //4: 一二三级分类   分类视图
        BaseCategoryView baseCategoryView = productFeignClient.
                getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //5:查询当前库存的平台属性
        List<SkuAttrValue> skuAttrValueList =
                productFeignClient.getSkuAttrValue(skuInfo.getId());
        //Stream 流  偷天换日
        List<SearchAttr> searchAttrList = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性ID
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            //平台属性的名称
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            //平台属性值的名称
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);//Goods 翻译成中文叫商品
        //添加索引
        goodsDao.save(goods);
    }

    //下架
    @Override
    public void onCancel(Long skuId) {//
        //删除索引
        goodsDao.deleteById(skuId);
    }
    //增加热度的分数
    @Override
    public void incrHotScore(Long skuId, Integer score) {

        String hotScore = "hotScore";
        //优化：  使用缓存来优化热度的分数
        //1:将增加的分 先保存到缓存中
          //缓存：Redis  五大数据类型
        //  String类型  Hash类型  Set类型  ZSet类型 List类型
        /**
         *  ZSet :
         *     小明   语文 89分  数学 92分  英语  100分
         *     小五   语文 100分  数学 90分  英语  67分
         *     小张
         *     小黄
         *     60人
         *  本项目zSet:
         *    hotScore :  1号 68分  2号 78 3号 90  ....  15号 80
         *
         */
        Double incrementScore = redisTemplate.
                opsForZSet().incrementScore(hotScore, skuId, score);
        if(incrementScore%10==0){
            //2:判断分数是否为 10或100或1000的整数 的倍数
            //3:再更新ES索引  否则不更新ES
            //3.1:先查询
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            //增加热度的分数
            goods.setHotScore(Math.round(incrementScore));
            //ES  索引库  底层是Lucene 特点：修改=删除+添加
            //3.2：save :  修改+添加   修改：传递ID 已经存在了 就是修改  ID不存在就是添加
            goodsDao.save(goods);
            //ES ： 查询方面非常快   缺点：在添加或删除或修改方面是超级慢的
            //例如：新华字典：制作费劲   使用是真方便   倒排索引法
            System.out.println("更新ES了");
        }else{
            System.out.println("更新缓存了");
        }

    }

    //开始搜索   代码难度超大   代码达到巅峰  ES 索引库
    @Override
    public SearchResponseVo search(SearchParam searchParam) {

        //1：构建搜索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //2：开始搜索  （到ES中）
            //SearchResponse：搜索响应 就是结果
            SearchResponse searchResponse = restHighLevelClient.
                    search(searchRequest, RequestOptions.DEFAULT);
            //3:解析响应结果
            SearchResponseVo vo = buildSearchResponse(searchResponse);
            //4:计算总页数  页面上是要使用的
            vo.setPageNo(searchParam.getPageNo());
            vo.setPageSize(searchParam.getPageSize());
            //总页数 = （总条数 + 每页数 - 1）/每页数
            //   总条数 7  每页数 3 =  共3页   （7+3-1)/3=3
            long totalPages = (vo.getTotal() + vo.getPageSize() - 1) / vo.getPageSize();
            vo.setTotalPages(totalPages);
            return vo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //结果解析
    private SearchResponseVo buildSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
        //1:品牌集合
        ParsedLongTerms tmIdAgg = searchResponse.getAggregations().get("tmIdAgg");
        //品牌结果集
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌ID
            long id = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
            tmVo.setTmId(id);
            //品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            tmVo.setTmName(tmName);
            //品牌Logo
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            tmVo.setTmLogoUrl(tmLogoUrl);
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);

        //2:平台属性集合
        ParsedNested attrsAgg = searchResponse.getAggregations().get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性ID
            long id = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
            attrVo.setAttrId(id);
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //平台属性值集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            List<String> attrValueList = attrValueAgg.getBuckets().stream().map(b -> {
                return ((Terms.Bucket) b).getKeyAsString();
            }).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);

        SearchHits hits = searchResponse.getHits();
        //4:总计录数
        long totalHits = hits.totalHits;
        System.out.println("总条数：" + totalHits);
        vo.setTotal(totalHits);

        SearchHit[] hits1 = hits.getHits();
        //Stream流 偷天换日
        //3:商品集合  高亮
        List<Goods> goodsList = Arrays.stream(hits1).map(hit -> {
            String sourceAsString = hit.getSourceAsString();
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //获取高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if(null != highlightFields && highlightFields.size() >0){
                HighlightField title = highlightFields.get("title");
                String h = title.fragments()[0].toString();
                //是有高亮的  使用高亮的标题名称 如果没有高亮 使用普通名称
                goods.setTitle(h);
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);

        return vo;
    }

    //构建搜索请求
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1:构建搜索请求对象
        SearchRequest searchRequest = new SearchRequest();//DSL: GET _search
        //2:创建 搜索资源构建对象
        SearchSourceBuilder searchSourceBuilder =
                       new SearchSourceBuilder();
        //3:默认 查询所有的索引
        //组合对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //3.1 关键词  由于是需要分词   matchQuery 匹配:1、先分词 2、再模糊查询
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword));
        }else{
            //如果关键词 没有值 可以临时查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //3.2 查询品牌  精准查询   品牌ID:品牌名称
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){// where name = '张三'
            String[] t = trademark.split(":");
            //组合品牌
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",t[0]));
        }
        //3.3 查询 平台属性（超难） 平台属性ID:平台属性值:平台属性名称
        String[] props = searchParam.getProps();
        if(null != props && props.length>0){
            //组合对象  大量的儿子
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

            for (String prop : props) {
                // prop :  平台属性ID:平台属性值名称:平台属性名称
                String[] p = prop.split(":");//长度是3
                if(null != p && p.length == 3){
                    //开始过滤
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(
                            QueryBuilders.termQuery("attrs.attrId",p[0]));
                    subBoolQuery.must(
                            QueryBuilders.termQuery("attrs.attrValue",p[1]));
                    //nested ：嵌套
                    boolQuery.must(QueryBuilders.
                            nestedQuery("attrs",subBoolQuery, ScoreMode.None));
                }
            }
            //组合平台属性
            boolQueryBuilder.filter(boolQuery);
        }
        //3.4:查询一二三级分类Id
        Long category1Id = searchParam.getCategory1Id();
        if(null != category1Id){
            boolQueryBuilder.filter(
                    QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null != category2Id){
            boolQueryBuilder.filter(
                    QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null != category3Id){
            boolQueryBuilder.filter(
                    QueryBuilders.termQuery("category3Id",category3Id));
        }
        //将组合对象设置到Query中去
        searchSourceBuilder.query(boolQueryBuilder);
        //分页
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo-1)*pageSize);//开始行
        searchSourceBuilder.size(pageSize);//每页数
        //排序    综合 ： 公平原则  ： 按照 热度 评分进行排序
        //        价格：  order=2:asc  或 2:desc
        //        综合 ： order=1:asc  或 1:desc
        // 1: 综合   2： 价格 3:新品 4：销量  5:评价
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] o = StringUtils.split(order, ":");
            String orderValue = "";
            switch (o[0]){
                case "1": orderValue = "hotScore"; break;
                case "2": orderValue = "price"; break;
                case "3": orderValue = "createTime"; break;
            }
            searchSourceBuilder.sort(orderValue,
                    "desc".equalsIgnoreCase(o[1])?SortOrder.DESC:SortOrder.ASC);
        }else{
            //排序是无值  走默认 综合   DESC
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }
        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                .preTags("<font style='color:red'>")
                .postTags("</font>")
                .field("title");
        searchSourceBuilder.highlighter(highlightBuilder);
        //品牌的桶聚合   group by  品牌ID  品牌名称 品牌Logo 页面需要
        //父子关系   tmId:父亲   品牌名称 品牌Logo ：儿子
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //平台属性的桶聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                )
        );
        System.out.println(searchSourceBuilder.toString());//DSL语句

        //4:搜索资源构建对象设置给搜索请求对象
        searchRequest.source(searchSourceBuilder);
        //5：设置查询的索引库的名称
        searchRequest.indices("goods");//GET goods/_search{}
        /**
         * GET goods/_search
         * {
         *     //条件
         *     //分页
         *     //高亮
         *     //排序
         *     //聚合
         * }
         */
        return searchRequest;
    }
}
